import { nextTick } from "vue";
import { inBrowser, getScrollParent } from "@vant/use";
import {
  remove,
  on,
  off,
  throttle,
  supportWebp,
  getDPR,
  getBestSelectionFromSrcset,
  hasIntersectionObserver,
  modeType,
  ImageCache
} from "./util.mjs";
import { isObject } from "../../utils/index.mjs";
import ReactiveListener from "./listener.mjs";
// 图片加载出错默认图片
const DEFAULT_URL = "";
// 以下事件都可以引起懒加载触发
const DEFAULT_EVENTS = [
  "scroll",
  "wheel",
  "mousewheel",
  "resize",
  "animationend",
  "transitionend",
  "touchmove"
];
// 视口模式 添加: 自定义指令(add/update) 组件(addLazyBox)
// 视口模式 移除: 自定义指令(update/remove) 组件(removeComponent) setMode observerHandler
// 事件模式 添加: 自定义指令(add) 组件(addLazyBox)
// 事件模式 移除: 自定义指令(remove) 组件(removeComponent)
const DEFAULT_OBSERVER_OPTIONS = {
  rootMargin: "0px",
  threshold: 0
};
function stdin_default() {
  return class Lazy {
    constructor({
      preLoad,
      error,
      throttleWait,
      preLoadTop,
      dispatchEvent,
      loading,
      attempt,
      silent = true,
      scale,
      listenEvents,
      filter,
      adapter,
      observer,
      observerOptions
    }) {
      this.mode = modeType.event; // 采用那种模式
      this.listeners = []; // lazy-component和lazy-image组件的this v-lazy自定义指令模拟组件实例类 new ReactiveListener (observer模式下都会被监听)
      this.targets = []; // 事件绑定对象list(使用组件的父元素或window)
      this.targetIndex = 0; 
      this.options = {
        silent, // 是否打印debug info
        dispatchEvent: !!dispatchEvent, // 是否触发 dom event
        throttleWait: throttleWait || 200, // 节流等待时间
        preLoad: preLoad || 1.3, // 预加载的宽高比例
        preLoadTop: preLoadTop || 0, // 视口判断量
        error: error || DEFAULT_URL,
        loading: loading || DEFAULT_URL,
        attempt: attempt || 3, // 重新请求次数
        scale: scale || getDPR(scale), // 缩放比例
        ListenEvents: listenEvents || DEFAULT_EVENTS, // 需要监听事件list
        supportWebp: supportWebp(),
        filter: filter || {}, // 图片URL过滤
        adapter: adapter || {}, // 适配器???
        observer: !!observer, // 视口观察器
        observerOptions: observerOptions || DEFAULT_OBSERVER_OPTIONS // 视口观察器配置
      };
      this.initEvent();
      // lazy自定义指令使用 缓存imgSrc
      this.imageCache = new ImageCache({ max: 200 });
      this.lazyLoadHandler = throttle(
        this.lazyLoadHandler.bind(this),
        this.options.throttleWait
      );
      // 初始化设定模式, 可动态修改???
      this.setMode(this.options.observer ? modeType.observer : modeType.event);
    }

    /**
     * update config
     * @param  {Object} config params
     * @return
     */
    config(options = {}) {
      Object.assign(this.options, options);
    }

    /**
     * output listener's load performance
     * @return {Array}
     */
    // 暂未使用
    performance() {
      return this.listeners.map((item) => item.performance());
    }

    

    /*
     * add image listener to queue
     * @param  {DOM} el
     * @param  {object} binding vue directive binding
     * @param  {vnode} vnode vue directive vnode
     * @return
     */
    // 自定义指令中 都会在listeners中插入 模拟组件实例ReactiveListener类
    // this.lazyLoadHandler();
    // nextTick(() => this.lazyLoadHandler());
    // 连续使用 渲染会引起链式反应???
    // 自定义指令v-lazy中钩子函数beforeMount
    add(el, binding, vnode) {
      // LazyContainer实例在update时也会使用该方法
      if (this.listeners.some((item) => item.el === el)) {
        this.update(el, binding);
        return nextTick(this.lazyLoadHandler);
      }
      const value = this.valueFormatter(binding.value);
      let { src } = value;
      nextTick(() => {
        src = getBestSelectionFromSrcset(el, this.options.scale) || src;
        this.observer && this.observer.observe(el);
        // modifiers第一个修饰符可以,指定父级refs或elID,来指定父级
        const container = Object.keys(binding.modifiers)[0];
        let $parent;
        if (container) {
          $parent = vnode.context.$refs[container];
          $parent = $parent ? $parent.$el || $parent : document.getElementById(container);
        }
        // 没有直接指定,直接获取可以滚动的父节点
        if (!$parent) {
          $parent = getScrollParent(el);
        }
        // 自定义指令没有组件实例  通过ReactiveListener类来模拟
        // 组件this实例和vnode不是一个东西
        const newListener = new ReactiveListener({
          bindType: binding.arg, // 指令的参数 :background-image
          $parent, // 父节点
          el, // 在lazy-image和lazy-component中为this.$el
          src, // 图片路基
          loading: value.loading, 
          error: value.error, 
          cors: value.cors,
          elRenderer: this.elRenderer.bind(this),
          options: this.options, // lazy.js中配置传入
          imageCache: this.imageCache // 自定义指令图片缓存
        });
        this.listeners.push(newListener);
        if (inBrowser) {
          this.addListenerTarget(window);
          this.addListenerTarget($parent);
        }
        this.lazyLoadHandler();
        nextTick(() => this.lazyLoadHandler());
      });
    }

    /**
     * update image src
     * @param  {DOM} el
     * @param  {object} vue directive binding
     * @return
     */
    // 自定义指令v-lazy/v-lazy-container中钩子函数updated
    update(el, binding, vnode) {
      const value = this.valueFormatter(binding.value);
      let { src } = value;
      src = getBestSelectionFromSrcset(el, this.options.scale) || src;
      const exist = this.listeners.find((item) => item.el === el);
      if (!exist) {
        this.add(el, binding, vnode);
      } else {
        // 跟新image上面data-src的值
        exist.update({
          src,
          error: value.error,
          loading: value.loading
        });
      }
      // v-lazy/v-lazy-container 更新会影响布局,需要跟新对应observer???
      if (this.observer) {
        this.observer.unobserve(el);
        this.observer.observe(el);
      }
      this.lazyLoadHandler();
      nextTick(() => this.lazyLoadHandler());
    }

    /**
     * remove listener form list
     * @param  {DOM} el
     * @return
     */
    // 自定义指令v-lazy/v-lazy-container中钩子函数unmounted
    // listener事件及其视口监听移除 
    remove(el) {
      if (!el)
        return;
      this.observer && this.observer.unobserve(el);
      const existItem = this.listeners.find((item) => item.el === el);
      if (existItem) {
        // 从listeners中移除existItem 并且手动销毁existItem实例
        this.removeListenerTarget(existItem.$parent);
        this.removeListenerTarget(window);
        remove(this.listeners, existItem);
        existItem.$destroy();
      }
    }

    // 动态切设定模式  event事件模式 observer观察器模式
    setMode(mode) {
      // 强制转为事件模式
      if (!hasIntersectionObserver && mode === modeType.observer) {
        mode = modeType.event;
      }
      this.mode = mode;
      if (mode === modeType.event) {
        // 移除observer对应观察对象 添加事件监听
        if (this.observer) {
          this.listeners.forEach((listener) => {
            this.observer.unobserve(listener.el);
          });
          this.observer = null;
        }
        this.targets.forEach((target) => {
          this.initListen(target.el, true);
        });
      } else {
        // 移除事件监听 observer添加对应观察对象
        this.targets.forEach((target) => {
          this.initListen(target.el, false);
        });
        this.initIntersectionObserver();
      }
    }

    /*
     * add lazy component to queue
     * @param  {Vue} vm lazy component instance
     * @return
     */
    // 在lazy-iamge及lazy-component组件中使用
    // 添加listeners及其视口监听 添加"父节点"对应事件合集
    addLazyBox(vm) {
      // vm为组件实例this
      this.listeners.push(vm);
      if (inBrowser) {
        // 有些时间只能绑定在window上, 所以window上都要添加一遍
        this.addListenerTarget(window);
        this.observer && this.observer.observe(vm.el);
        if (vm.$el && vm.$el.parentNode) {
          this.addListenerTarget(vm.$el.parentNode);
        }
      }
    }

    /*
     * remove lazy components form list
     * @param  {Vue} vm Vue instance
     * @return
     */
    // 在lazy-iamge及lazy-component组件中使用
    // vm为组件实例this
    // 移除listeners 及事件监听
    removeComponent(vm) {
      if (!vm)
        return;
      remove(this.listeners, vm);
      this.observer && this.observer.unobserve(vm.el);
      if (vm.$parent && vm.$el.parentNode) {
        this.removeListenerTarget(vm.$el.parentNode);
      }
      this.removeListenerTarget(window);
    }

    /*
     *** Private functions ***
     */
    /*
     * add listener target
     * @param  {DOM} el listener target
     * @return
     */
    // el为使用组件或 使用v-lazy组件的父组件或window
    addListenerTarget(el) {
      if (!el)
        return;
      // el可能重复,el为使用节点的父级节点  会用有多个节点的父级节点相同
      let target = this.targets.find((target2) => target2.el === el);
      if (!target) {
        target = {
          el,
          id: ++this.targetIndex,
          childrenCount: 1,
          listened: true
        };
        // 初次绑定事件合集
        this.mode === modeType.event && this.initListen(target.el, true);
        this.targets.push(target);
      } else {
        target.childrenCount++;
      }
      return this.targetIndex;
    }

    /*
     * remove listener target or reduce target childrenCount
     * @param  {DOM} el or window
     * @return
     */
    // 判读target下面是否还有使用lazy组件或自定义指令
    // 当没有时将target从targets中移除,同时移除target上的事件合集
    removeListenerTarget(el) {
      this.targets.forEach((target, index) => {
        if (target.el === el) {
          target.childrenCount--;
          // 当父节点target下面没有使用需求时,移除事件合集监听
          if (!target.childrenCount) {
            this.initListen(target.el, false);
            this.targets.splice(index, 1);
            target = null;
          }
        }
      });
    }

    /*
     * add or remove eventlistener
     * @param  {DOM} el DOM or Window
     * @param  {boolean} start flag
     * @return
     */
    // 给el添加/移除 事件合集
    // setmode/addListenerTarget/removeListenerTarget时使用
    initListen(el, start) {
      this.options.ListenEvents.forEach(
        (evt) => (start ? on : off)(el, evt, this.lazyLoadHandler)
      );
    }

    // 暂未使用
    initEvent() {
      this.Event = {
        listeners: {
          loading: [],
          loaded: [],
          error: []
        }
      };
      this.$on = (event, func) => {
        if (!this.Event.listeners[event])
          this.Event.listeners[event] = [];
        this.Event.listeners[event].push(func);
      };
      this.$once = (event, func) => {
        const on2 = (...args) => {
          this.$off(event, on2);
          func.apply(this, args);
        };
        this.$on(event, on2);
      };
      this.$off = (event, func) => {
        if (!func) {
          if (!this.Event.listeners[event])
            return;
          this.Event.listeners[event].length = 0;
          return;
        }
        remove(this.Event.listeners[event], func);
      };
      this.$emit = (event, context, inCache) => {
        if (!this.Event.listeners[event])
          return;
        this.Event.listeners[event].forEach((func) => func(context, inCache));
      };
    }

    /**
     * find nodes which in viewport and trigger load
     * @return
     */
    // 事件合集绑定触发事件 或 自定义指令add/update手动触发
    lazyLoadHandler() {
      const freeList = [];
      this.listeners.forEach((listener) => {
        if (!listener.el || !listener.el.parentNode) {
          freeList.push(listener);
        }
        // 使用lazy-image/lazy-component 或ReactiveListener实例中checkInView检测是否初次可见
        const catIn = listener.checkInView();
        if (!catIn)
          return;
        listener.load();
      });
      freeList.forEach((item) => {
        remove(this.listeners, item);
        item.$destroy();
      });
    }

    /**
     * init IntersectionObserver
     * set mode to observer
     * @return
     */
    // 添加视口元素交叉观察器
    initIntersectionObserver() {
      if (!hasIntersectionObserver) {
        return;
      }
      this.observer = new IntersectionObserver(
        this.observerHandler.bind(this),
        this.options.observerOptions
      );
      // 逐个添加观察对象
      if (this.listeners.length) {
        this.listeners.forEach((listener) => {
          this.observer.observe(listener.el);
        });
      }
    }

    /**
     * init IntersectionObserver
     * @return
     */
    // 观察器触发的相关事件
    observerHandler(entries) {
      entries.forEach((entry) => {
        if (entry.isIntersecting) { // 当元素进入视口时  
          // entery.target触发的el
          this.listeners.forEach((listener) => {
            if (listener.el === entry.target) {
              // 如果该imag已经loaded完毕,将其移除
              if (listener.state.loaded)
                return this.observer.unobserve(listener.el);
              // 开始调用该实例的load方法
              listener.load();
            }
          });
        }
      });
    }

    /**
     * set element attribute with image'url and state
     * @param  {object} lazyload listener object
     * @param  {string} state will be rendered
     * @param  {bool} inCache  is rendered from cache
     * @return
     */
    // 自定指令使用
    // Listener为lazy组件  state为当前状态 cache是否使用缓存
    elRenderer(listener, state, cache) {
      if (!listener.el)
        return;
      const { el, bindType } = listener;
      let src;
      switch (state) {
        case "loading":
          src = listener.loading;
          break;
        case "error":
          src = listener.error;
          break;
        default:
          ({ src } = listener);
          break;
      }
      // 根据bindType来判读 是懒加载图片还是背景图片
      if (bindType) {
        el.style[bindType] = 'url("' + src + '")';
      } else if (el.getAttribute("src") !== src) {
        el.setAttribute("src", src);
      }
      el.setAttribute("lazy", state);
      this.$emit(state, listener, cache);
      this.options.adapter[state] && this.options.adapter[state](listener, this.options);
      // 触发自定义事件
      if (this.options.dispatchEvent) {
        const event = new CustomEvent(state, {
          detail: listener
        });
        el.dispatchEvent(event);
      }
    }

    /**
     * generate loading loaded error image url
     * @param {string} image's src
     * @return {object} image's loading, loaded, error url
     */
    // 格式化图片懒加载配置
    // lazy-iamge和自定义指令支持传入object
    // 返回同一格式 {src路径 loading:加载中图片  error:错误图片}
    valueFormatter(value) {
      let src = value;
      let { loading, error } = this.options;
      if (isObject(value)) {
        if (process.env.NODE_ENV !== "production" && !value.src && !this.options.silent) {
          console.error("[@vant/lazyload] miss src with " + value);
        }
        ({ src } = value);
        loading = value.loading || this.options.loading;
        error = value.error || this.options.error;
      }
      return {
        src,
        loading,
        error
      };
    }
  };
}
export {
  stdin_default as default
};
